/********************************************************************/
/*          MultiSio.c                                              */
/*            Multi-play Communication Library                      */
/*      (Timer Interrupt Send & Non-adjustment of Synchronization)  */
/*           (Support for 32bit Serial Communication Multi-load)    */
/*                                                                  */
/*          Copyright (C) 1999-2001 NINTENDO Co.,Ltd.               */
/********************************************************************/
//#define CODE32
#include "MultiSio.h"
#include "Sio32MultiLoad.h"


/*-------------------- Global Variable  ----------------------------*/

MultiSioArea     Ms;            // Multi-play Communication Work Area

#ifdef MULTI_SIO_DI_FUNC_FAST
u32 RecvFuncBuf[0x40/4];        // Receive Data/Check Buffer Change Routine RAM Execution Buffer
u32 IntrFuncBuf[0x120/4];       // Interrupt Routine RAM Execution Buffer
#endif

static const u8 MultiSioLib_Var[]="MultiSio4Sio32Load010214";


/*------------------------------------------------------------------*/
/*                      Multi-play Communication Initialization     */
/*------------------------------------------------------------------*/
extern u32 MultiSioRecvBufChange(void);

void MultiSioInit(u32 ConnectedFlags)
{
    int     i;

    *(vu16 *)REG_IME = 0;
    *(vu16 *)REG_IE &= ~(SIO_INTR_FLAG                  // Disable SIO & Timer Interrupt 
                       | MULTI_SIO_TIMER_INTR_FLAG);
    *(vu16 *)REG_IME = 1;

    *(vu16 *)REG_RCNT  = R_SIO_MASTER_MODE;
    *(vu32 *)REG_SIOCNT  = SIO_MULTI_MODE;
    *(vu16 *)REG_SIOCNT |= SIO_IF_ENABLE | MULTI_SIO_BAUD_RATE_NO;

    CpuClear(0, &Ms, sizeof(Ms), 32);                   // Clear Multi-play Communication Work Area

#ifdef MULTI_SIO_DI_FUNC_FAST                           // Copy Function
    CpuCopy(MultiSioRecvBufChange, RecvFuncBuf, sizeof(RecvFuncBuf), 32);
    CpuCopy(MultiSioIntr,          IntrFuncBuf, sizeof(IntrFuncBuf), 32);
#endif

    Ms.ConnectedFlags = ConnectedFlags;

    Ms.SendBufCounter     = -1;

    Ms.NextSendBufp    = (u16 *)&Ms.SendBuf[0];         // Set Send Buffer Pointer
    Ms.CurrentSendBufp = (u16 *)&Ms.SendBuf[1];

    for (i=0; i<4; i++) {                               // Set Receive Buffer Pointer
        Ms.CurrentRecvBufp[i] = (u16 *)&Ms.RecvBuf[i][0];
        Ms.LastRecvBufp[i]    = (u16 *)&Ms.RecvBuf[i][1];
        Ms.RecvCheckBufp[i]   = (u16 *)&Ms.RecvBuf[i][2];
    }

    *(vu16 *)REG_IME = 0;
    *(vu16 *)REG_IE |= SIO_INTR_FLAG;                   // Enable SIO Interrupt
    *(vu16 *)REG_IME = 1;
}


/*------------------------------------------------------------------*/
/*                      Start Multi-play Communication              */
/*------------------------------------------------------------------*/

void MultiSioStart(void)
{
    if (Ms.Type)    Ms.StartFlag = 1;                   // Set Start Flag
}

/*------------------------------------------------------------------*/
/*                      Stop Multi-play Communication               */
/*------------------------------------------------------------------*/

void MultiSioStop(void)
{
    *(vu16 *)REG_IME = 0;
    *(vu16 *)REG_IE &= ~(SIO_INTR_FLAG                  // Disable SIO & Timer Interrupt
                       | MULTI_SIO_TIMER_INTR_FLAG);
    *(vu16 *)REG_IME = 1;

    *(vu16 *)REG_SIOCNT = SIO_MULTI_MODE                // Stop SIO
                        | MULTI_SIO_BAUD_RATE_NO;
    *(vu32 *)REG_MULTI_SIO_TIMER                        // Stop Timer
                        = MULTI_SIO_TIMER_COUNT;

    *(vu16 *)REG_IF = SIO_INTR_FLAG                     // Reset IF
                    | MULTI_SIO_TIMER_INTR_FLAG;

    Ms.StartFlag = 0;                                   // Reset Start Flag
}


/*------------------------------------------------------------------*/
/*                      Multi-play Communication Main               */
/*------------------------------------------------------------------*/

u32 MultiSioMain(void *Sendp, void *Recvp, u32 LoadRequest)
{
    SioMultiCnt     SioCntBak;
    int             i, ii;

    switch (Ms.State) {
        case 0: SioCntBak = *(SioMultiCnt *)REG_SIOCNT;     // Check Connection
                if (!SioCntBak.ID) {
                    if (!SioCntBak.SD || SioCntBak.Enable)    break;
                    if (!SioCntBak.SI && Ms.SendBufCounter == -1) {
                        *(vu16 *)REG_IME = 0;
                        *(vu16 *)REG_IE &= ~SIO_INTR_FLAG;              // Disable SIO Interrupt
                        *(vu16 *)REG_IE |=  MULTI_SIO_TIMER_INTR_FLAG;  // Enable Timer Interrupt
                        *(vu16 *)REG_IME = 1;

                        ((SioMultiCnt *)REG_SIOCNT)->IF_Enable = 0;     // Reset SIO-IFE
                        *(vu16 *)REG_IF  =  SIO_INTR_FLAG               // Reset IF
                                         |  MULTI_SIO_TIMER_INTR_FLAG;

                        *(vu32 *)REG_MULTI_SIO_TIMER                    // Timer Initialization
                                         = MULTI_SIO_TIMER_COUNT;

                        Ms.Type = SIO_MULTI_PARENT;
                        ((SioMultiCnt *)REG_SIOCNT)->Enable = 1;        // Start Send
                    }
                }
                Ms.State = 1;
        case 1: MultiSioRecvDataCheck(Recvp);               // Check Receive Data
                MultiSioSendDataSet(Sendp, LoadRequest);    // Set Send Data
                break;
    }

    Ms.SendFrameCounter++;

    return      Ms.RecvSuccessFlags
              | Ms.LoadEnable << 4
              | Ms.LoadRequest << 5
              | Ms.LoadSuccessFlag << 6
              | (Ms.Type == SIO_MULTI_PARENT) << 7
              | Ms.ConnectedFlags << 8;
}


/*------------------------------------------------------------------*/
/*                      Set Send Data                               */
/*------------------------------------------------------------------*/

void MultiSioSendDataSet(void *Sendp, u32 LoadRequest)
{
    s32     CheckSum = 0;
    int     i;

    ((MultiSioPacket *)Ms.NextSendBufp)->LoadRequest = LoadRequest;
    ((MultiSioPacket *)Ms.NextSendBufp)->DownloadSuccessFlag = S32ml.DownloadSuccessFlag;
    ((MultiSioPacket *)Ms.NextSendBufp)->LoadSuccessFlag = Ms.LoadSuccessFlag;

    ((MultiSioPacket *)Ms.NextSendBufp)->FrameCounter = (u8 )Ms.SendFrameCounter;
    ((MultiSioPacket *)Ms.NextSendBufp)->RecvErrorFlags =  Ms.ConnectedFlags ^ Ms.RecvSuccessFlags;
    ((MultiSioPacket *)Ms.NextSendBufp)->CheckSum = 0;

    CpuCopy(Sendp, (u8 *)&Ms.NextSendBufp[2], MULTI_SIO_BLOCK_SIZE, 32);  // Set Send Data

    for (i=0; i<sizeof(MultiSioPacket)/2 - 2; i++)      // Calculate Checksum Send Data
        CheckSum += Ms.NextSendBufp[i];
    ((MultiSioPacket *)Ms.NextSendBufp)->CheckSum = ~CheckSum;

    if (Ms.Type)
        *(vu16 *)REG_MULTI_SIO_TIMER_H = 0;             // Stop Timer

    Ms.SendBufCounter = -1;

    if (Ms.Type && Ms.StartFlag)
        *(vu16 *)REG_MULTI_SIO_TIMER_H                  // Start Timer
                             = (TMR_PRESCALER_1CK
                              | TMR_IF_ENABLE | TMR_ENABLE) >> 16;
}

/*------------------------------------------------------------------*/
/*                     Check Receive Data                           */
/*------------------------------------------------------------------*/

u32 MultiSioRecvDataCheck(void *Recvp)
{
    u32 (*MultiSioRecvBufChangeOnRam)(void) = (u32 (*)(void))RecvFuncBuf;
    s32      CheckSum;
    vu32     RecvCheck = 0;
    u8       SyncRecvFlagBak[4];
    u8       CounterDiff;
    u16     *BufpTmp;
    int      i, ii;


#ifdef MULTI_SIO_DI_FUNC_FAST                           // Update Receive Data/Check Buffer
    *(u32 *)SyncRecvFlagBak = MultiSioRecvBufChangeOnRam();
#else
    *(vu16 *)REG_IME = 0;                               // Disable Interrupt (Approx. 80 Clocks)

    for (i=0; i<4; i++) {
        BufpTmp = Ms.RecvCheckBufp[i];                  // Update Receive Data/Check Buffer
        Ms.RecvCheckBufp[i] = Ms.LastRecvBufp[i];
        Ms.LastRecvBufp[i] = BufpTmp;
    }

    *(vu16 *)REG_IME = 1;                               // Enable Interrupt
#endif

    Ms.RecvSuccessFlags = 0;

    for (i=0; i<4; i++) {
        CheckSum = 0;                                   // Calculate Checksum Receive Data
        for (ii=0; ii<sizeof(MultiSioPacket)/2 - 2; ii++)
            CheckSum +=  Ms.RecvCheckBufp[i][ii];

        if (SyncRecvFlagBak[i])                         // Receive Success Confirmation
            if ((s16 )CheckSum == -1) {
                Ms.RecvSuccessFlags     |= 1 << i;
                Ms.DownloadSuccessFlags |= ((MultiSioPacket *)Ms.RecvCheckBufp[i])->DownloadSuccessFlag << i;
                CpuCopy(&((u8 *)Ms.RecvCheckBufp[i])[4],
                        &((u8 *)Recvp)[i*MULTI_SIO_BLOCK_SIZE], MULTI_SIO_BLOCK_SIZE, 32);
            }

        CpuClear(0, &((u8 *)Ms.RecvCheckBufp[i])[4], MULTI_SIO_BLOCK_SIZE, 32);
    }

    Ms.ConnectedFlags |= Ms.RecvSuccessFlags;           // Set Connect Complete Flag


    if (Ms.RecvSuccessFlags & 1) {
        if (Ms.Type == SIO_MULTI_PARENT) {
            if (Ms.RecvSuccessFlags & 0x3)              // Enable Load
                if (Ms.RecvSuccessFlags == Ms.ConnectedFlags)
                    Ms.LoadEnable = 1;
                                                        // Check Load
            if ((Ms.DownloadSuccessFlags & 0xe) == (Ms.ConnectedFlags & 0xe))
                Ms.LoadSuccessFlag = 1;
        } else  Ms.LoadSuccessFlag |= ((MultiSioPacket *)Ms.RecvCheckBufp[0])->LoadSuccessFlag;

                                                        // Request Load
        Ms.LoadRequest |= ((MultiSioPacket *)Ms.RecvCheckBufp[0])->LoadRequest;
    }

    return Ms.RecvSuccessFlags;
}


/*==================================================================*/
/*                  Multi-play Communication Interrupt Routine      */
/*==================================================================*/

#ifndef MULTI_SIO_DI_FUNC_FAST

void MultiSioIntr(void)
{
    u16     RecvTmp[4];
    u16    *BufpTmp;
    int     i, ii;


    // Save Receive Data

    *(u64 *)RecvTmp = *(u64 *)REG_SIOMLT_RECV;


    // Send Data Processing

    if (Ms.SendBufCounter == -1) {                      // Set Synchronized Data
        ((SioMultiCnt *)REG_SIOCNT)->Data = MULTI_SIO_SYNC_DATA;

        BufpTmp = Ms.CurrentSendBufp;                   // Change Send Buffer
        Ms.CurrentSendBufp = Ms.NextSendBufp;
        Ms.NextSendBufp = BufpTmp;
    } else if (Ms.SendBufCounter >= 0) {                // Set Send Data
        ((SioMultiCnt *)REG_SIOCNT)->Data = Ms.CurrentSendBufp[Ms.SendBufCounter];
    }
    if (Ms.SendBufCounter < (s32 )(sizeof(MultiSioPacket)/2 - 1))  Ms.SendBufCounter++;


    // Receive Data Processing (Max. Approx. 350 Clocks/Included in wait period)

    for (i=0; i<4; i++) {
        if (RecvTmp[i] == MULTI_SIO_SYNC_DATA
         && Ms.RecvBufCounter[i] > (s32 )(sizeof(MultiSioPacket)/2 - 3)) {
            Ms.RecvBufCounter[i] = -1;
        } else {
            Ms.CurrentRecvBufp[i][Ms.RecvBufCounter[i]] = RecvTmp[i];
                                                        // Store Receive Data
            if (Ms.RecvBufCounter[i] == (s32 )(sizeof(MultiSioPacket)/2 - 3)) {
                BufpTmp = Ms.LastRecvBufp[i];           // Change Receive Buffer
                Ms.LastRecvBufp[i] = Ms.CurrentRecvBufp[i];
                Ms.CurrentRecvBufp[i] = BufpTmp;
                Ms.SyncRecvFlag[i] = 1;                 // Receive Complete Flag
            }
        }
        if (Ms.RecvBufCounter[i] < (s32 )(sizeof(MultiSioPacket)/2 - 1))  Ms.RecvBufCounter[i]++;
    }


    // Start Master Send

    if (Ms.Type == SIO_MULTI_PARENT) {
        *(vu16 *)REG_MULTI_SIO_TIMER_H = 0;             // Stop Timer
        *(vu16 *)REG_SIOCNT |= SIO_ENABLE;              // Restart Send
        *(vu16 *)REG_MULTI_SIO_TIMER_H                  // Restart Timer
                             = (TMR_PRESCALER_1CK
                              | TMR_IF_ENABLE | TMR_ENABLE) >> 16;
    }
}

#endif

